import express from 'express'
import { createServer } from 'http'
import { fileURLToPath } from 'url'
import path from 'path'
import { Server } from 'socket.io'
import sqlite3 from 'sqlite3'
import { open } from 'sqlite'
import { availableParallelism } from 'os'
import cluster from 'cluster'
import { createAdapter, setupPrimary } from '@socket.io/cluster-adapter'

const db = await open({
  filename: 'chat.db',
  driver: sqlite3.Database
})

await db.exec(`
    CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        client_offset TEXT UNIQUE,
        content TEXT
    );
  `)

if (cluster.isPrimary) {
  const numCPUs = availableParallelism()
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork({
      PORT: 3000 + i
    })
  }
  setupPrimary()
} else {
  const app = express()
  const server = createServer(app)
  const io = new Server(server, {
    connectionStateRecovery: {},
    adapter: createAdapter()
  })

  const __dirname = path.dirname(fileURLToPath(import.meta.url))

  app.get('/', (req, res) => {
    res.sendFile(path.resolve(__dirname, 'index.html'))
  })

  io.on('connection', async socket => {
    console.log('a user connected')

    socket.on('disconnect', () => {
      console.log('user disconnected')
    })

    socket.on('chat message', async (msg, clientOffset, callback) => {
      let result
      try {
        result = await db.run(
          'INSERT INTO messages (content, client_offset) VALUES (?, ?)',
          msg,
          clientOffset
        )
      } catch (e) {
        if (e.errno === 19 /* SQLITE_CONSTRAINT */) {
          callback()
        } else {
        }
        return
      }
      io.emit('chat message', msg, result.lastID)
    })

    if (!socket.recovered) {
      try {
        await db.each(
          'SELECT id, content FROM messages WHERE id > ?',
          [socket.handshake.auth.serverOffset || 0],
          (_err, row) => {
            socket.emit('chat message', row.content, row.id)
          }
        )
      } catch (e) {}
    }
  })

  const port = process.env.PORT
  server.listen(port, () => {
    console.log(`server running at http://localhost:${port}`)
  })
}
